package edu.hawaii.nearestneighbor.classifiers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import edu.hawaii.nearestneighbor.classifications.ConfClassification;
import edu.hawaii.nearestneighbor.util.DataPoint;
import edu.hawaii.nearestneighbor.util.NNutil;

/**
 * Provides implementation of the statistical confidence classifier.
 * 
 * @author Robert Puckett
 * 
 */
public class StatConfClassifier implements Classifier {

  /**
   * Contains the confidence threshold.
   */
  private double confidence;

  /**
   * Classifies the point using the statistical confidence algorithm.
   * 
   * @param data the set of training data.
   * @param test the point to be classified.
   * @return the classification of test.
   * @see edu.hawaii.nearestneighbor.Classification#Classify(java.util.List,
   *      edu.hawaii.nearestneighbor.DataPoint)
   */
  public ConfClassification classify(List<DataPoint> data, DataPoint test) {
    // TODO Auto-generated method stub

    return new ConfClassification();
  }

  /**
   * Classifies the point using statistical confidence.
   * 
   * @param data the set of training data.
   * @param test the point to be classified.
   * @param distancetype distance type used.
   * @return the classification of the test point.
   * @see edu.hawaii.nearestneighbor.Classification#Classify(java.util.List,
   *      edu.hawaii.nearestneighbor.DataPoint, int)
   */
  public ConfClassification classify(List<DataPoint> data, DataPoint test, int distancetype) {
    // TODO Auto-generated method stub

    // return classifyWithConfidence(data, test, threshold, startK);
    return new ConfClassification();
  }

  /**
   * Calculates the statistical confidence of a set of DataPoints.
   * 
   * @param points A set of DataPoints.
   * @return The statistical confidence of a set of DataPoints.
   */
  public static double statConfidence(List<DataPoint> data) {
    // Stores the unique states and the count of their occurence in the list
    Map<Integer, Integer> mapping = new HashMap<Integer, Integer>();
    for (DataPoint c : data) {
      if (mapping.containsKey(c.state)) {
        mapping.put(c.state, mapping.get(c.state) + 1);
      }
      else {
        mapping.put(c.state, 1);
      }
    }

    // Find the max count
    double difference = 0.0;
    int earlyKey = 0;

    boolean start = true;
    for (Map.Entry<Integer, Integer> c : mapping.entrySet()) {
      if (start) {
        start = false;
        earlyKey = c.getKey();
        difference = c.getValue();
      }
      else {
        if (c.getKey() > earlyKey) {
          difference = c.getValue() - difference;
        }
        else {
          difference = difference - c.getValue();
        }
      }
    }

    // return Math.abs((Math.abs(difference) - 1.0)/ Math.sqrt(data.size()));
    // difference = Math.abs(difference);
    // if ((difference - 1.0) < 0) {
    // return 0.0;
    // }
    double value = (((double) Math.abs(difference) - 1.0) / Math.sqrt(data.size()));
    double lower = erf(value,16); // lower bound on confidence measure
    //return lower;
    //return lower;
   // System.out.println(lower);
    return lower;
    
    // return (Math.abs(difference)/data.size());
  }

  /**
   * Classifies the Point according to its statistical confidence.
   * 
   * @param points A set of DataPoints closest to the point being classified.
   * @return The statistical confidence of a set of DataPoints with the associated state.
   */
  public static ConfClassification classifyWithConfidence(List<DataPoint> data, DataPoint test,
      Double threshold, Integer startK) {
    List<DataPoint> near = new ArrayList<DataPoint>();
    DoubleValueHolder conf = new DoubleValueHolder();
    conf.value = 0.0;
    near = findKConfNearest(data, test, threshold, startK, conf);

    return new ConfClassification(NNutil.modeState(near), near.size(), conf.value);
  }

  /**
   * Finds the nearest points using statistical confidence and may not be the same number as the
   * start k.
   * 
   * @param data the set of training data.
   * @param threshold the threshold value for confidence.
   * @param startK the starting value for k.
   * @param conf the final confidence value.
   * @return the set of points nearest to the test point with sufficiently high confidence value.
   */
  public static List<DataPoint> findKConfNearest(List<DataPoint> data, DataPoint test,
      Double threshold, Integer startK, DoubleValueHolder conf) {
    int k = startK;
    double iconf = 0.0;
    int iteration = 0;
    int maxIter = data.size();// (int) Math.sqrt(data.size());

    List<DataPoint> result = new ArrayList<DataPoint>();
    TreeMap<Double, DataPoint> dist2point = new TreeMap<Double, DataPoint>();

    for (DataPoint c : data) {
      dist2point.put(NNutil.euclideanDistance(c, test), c);
    }
    double first = 0.0;

    while (result.size() < k) {
      first = dist2point.firstKey();
      if (test == dist2point.get(first)) // want to see if they're the same instance
      {
        dist2point.remove(first);
        continue;
      }
      result.add(dist2point.remove(first));
    }
    iconf = statConfidence(result);
    while ((iconf < threshold) && (k < maxIter)) {
      k = k + 1;
      while (result.size() < k) {
        first = dist2point.firstKey();
        if (test == dist2point.get(first)) // want to see if they're the same instance
        {
          dist2point.remove(first);
          continue;
        }
        result.add(dist2point.remove(first));
      }
      iconf = statConfidence(result);
    //  System.out.println(iconf);
      iteration++;
    }
    
    if (iteration == maxIter) {
      System.out.println("Warning: Max iterations met. (sqrt of list size).");
    }
    conf.value = iconf;

    return result;

  }

  /**
   * Returns the taylor series approximation to the error function of x.
   * 
   * @param x the random variable.
   * @return the taylor series approximation to the error function of x.
   */
  public static double erf(double x, int terms) {
    if (x > 2) {
      return 1.0;
    }
    if (x < -2) {
      return -1.0;
    }
    
    double result = 0.0;
    double constant = 2.0 / Math.sqrt(Math.PI);

    double topPower = 0.0;
    double top = 0.0;
    double bottom = 0.0;
    for (int n = 0; n< terms; n++) {
      
      topPower = ((2*n)+1.0);
      top = Math.pow(x,topPower);
      bottom = (double)((2*n)+1.0)*fact(n);
      double sign = Math.pow(-1.0,(double)n);
      //System.out.println("i: " + sign + "*(" + x + "^" + topPower + ") / " + bottom);
      
      result = result + sign*top/bottom;
      //System.out.println("i:" result);
      //System.out.println("i " + (Math.pow(-1.0,(double)n)*Math.pow(x,(double)(2*n+1.0)))/((double)(2*n+1.0)*fact(n)));
    }
    //result = x - (Math.pow(x, 3.0) / 3.0) + (Math.pow(x, 5.0) / 10.0) - (Math.pow(x, 7.0) / 42.0)
    //    + (Math.pow(x, 9.0) / 216.0);

    return constant * result;
  }

  public static double gammaCDF(double x, double mean, double sqrtvar) {
    double value = 0.0;
    value = 0.5 * (1.0 + erf((x-mean)/(Math.sqrt(2.0)*sqrtvar),9));
    return value;
  }
  
  /**
   * Calculates the factorial of x.
   * @param x an integer.
   * @return the factorial of x.
   */
  public static int fact(int x) {
    if (x < 2) {
      return 1;
    }
    return x*fact(x-1);
  }
  
  /**
   * Holds a double value so that it can be used as pass by reference parameter.
   * 
   * @author Robert Puckett
   * 
   */
  public static class DoubleValueHolder {
    public double value;

    public DoubleValueHolder() {

    }
  }
}
